package com.shun.gateway.handler;

import com.shun.gateway.exception.ApiGatewayException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import static org.springframework.web.reactive.function.server.RequestPredicates.all;

/**
 * 统一异常拦截处理，针对于application/json
 *
 * @author clever
 * @since 2021-09-22
 */
@Slf4j
public class ShunWebFluxErrorHandler extends DefaultErrorWebExceptionHandler {

    private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);

    /**
     * @param errorAttributes
     * @param resources
     * @param errorProperties
     * @param applicationContext
     */
    public ShunWebFluxErrorHandler(ErrorAttributes errorAttributes, WebProperties.Resources resources, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resources, errorProperties, applicationContext);
    }

    /**
     * 获取错误信息
     *
     * @param request
     * @param options
     * @return
     */
    protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        log.error("错误URL：{},x-token:{}",request.uri(),request.headers().firstHeader("x-token"));
        Map<String, Object> errorAttributes = new HashMap<String, Object>();
        Throwable error = super.getError(request);
        if (error instanceof ApiGatewayException) {
            ApiGatewayException apiGatewayException = (ApiGatewayException) error;
            errorAttributes.put("code", apiGatewayException.getCode());
            errorAttributes.put("msg", apiGatewayException.getMessage());
        } else if (error instanceof NotFoundException) {
            errorAttributes.put("code", "404");
            errorAttributes.put("msg", HttpStatus.NOT_FOUND.getReasonPhrase());
        } else if (error instanceof ResponseStatusException) {
            ResponseStatusException exception = (ResponseStatusException) error;
            HttpStatus status = exception.getStatus();
            errorAttributes.put("code", status.value());
            errorAttributes.put("msg", status.getReasonPhrase());
        } else {
            errorAttributes.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
            errorAttributes.put("msg", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
        }
        log.debug("errorAttributes:{},异常信息:{}", errorAttributes,error.getMessage());
        return errorAttributes;
    }

    /**
     * 路由信息
     *
     * @param errorAttributes
     * @return
     */
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
    }

    /**
     * @param request
     * @return
     */
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
        log.debug("error:{}", error);
        return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(error));
    }

    /**
     * 根据业务状态码返回通讯状态码
     *
     * @param errorAttributes
     * @return
     */
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        Object codeObj = errorAttributes.get("code");
        log.debug("code:{}", codeObj);
        if (codeObj instanceof Integer) {
            return (int) codeObj;
        } else {
            return HttpStatus.INTERNAL_SERVER_ERROR.value();
        }
    }
}
